#include "our_gl.h"

mat<4, 4> ModelView;
mat<4, 4> Viewport;
mat<4, 4> Projection;

void viewport(const int x, const int y, const int w, const int h) {
    Viewport = {{{w / 2., 0, 0, x + w / 2.}, {0, h / 2., 0, y + h / 2.}, {0, 0, 1, 0}, {0, 0, 0, 1}}};
}

void projection(const double f) {
    Projection = {{{1, 0, 0, 0}, {0, -1, 0, 0}, {0, 0, 1, 0}, {0, 0, -1 / f, 0}}};
}

void write_log(const std::string &msg) {
    std::ofstream logfile("../log.txt", std::ios::app);
    if (logfile.is_open()) {
        logfile << msg << std::endl;
    } else {
        std::cerr << "Could not open log file" << std::endl;
    }
}

// 世界空间转换为视图空间
void lookat(const vec3 &eye, const vec3 &center, const vec3 &up) {
    write_log("vec3 eye : " + eye.to_string());
    write_log("center : " + center.to_string());
    write_log("up : " + up.to_string());
    const vec3 z = (center - eye).normalized();
    write_log("z : " + z.to_string());
    const vec3 x = cross(up, z).normalized();
    write_log("x : " + x.to_string());
    const vec3 y = cross(z, x).normalized();
    write_log("y : " + y.to_string());
    const mat<4, 4> Minv = {{{x.x, x.y, x.z, 0}, {y.x, y.y, y.z, 0}, {z.x, z.y, z.z, 0}, {0, 0, 0, 1}}};
    write_log("Minv : " + Minv.to_string());
    const mat<4, 4> Tr = {{{1, 0, 0, -eye.x}, {0, 1, 0, -eye.y}, {0, 0, 1, -eye.z}, {0, 0, 0, 1}}};
    write_log("Tr : " + Tr.to_string());
    ModelView = Minv * Tr;
    write_log("ModelView : " + ModelView.to_string());
}

vec3 barycentric(const vec2 tri[3], const vec2 P) {
    mat<3, 3> ABC = {{embed<3>(tri[0]), embed<3>(tri[1]), embed<3>(tri[2])}};
    if (ABC.det() < 1e-3) return {-1, 1, 1};
    return ABC.invert_transpose() * embed<3>(P);
}

void triangle(const vec4 clip_verts[3], IShader &shader, TGAImage &image, std::vector<double> &zbuffer) {
    vec4 pts[3] = {Viewport * clip_verts[0], Viewport * clip_verts[1], Viewport * clip_verts[2]};
    vec2 pts2[3] = {proj<2>(pts[0] / pts[0][3]), proj<2>(pts[1] / pts[1][3]), proj<2>(pts[2] / pts[2][3])};

    int bboxmin[2] = {image.width() - 1, image.height() - 1};
    int bboxmax[2] = {0, 0};
    for (auto &i: pts2)
        for (int j = 0; j < 2; j++) {
            bboxmin[j] = std::min(bboxmin[j], static_cast<int>(i[j]));
            bboxmax[j] = std::max(bboxmax[j], static_cast<int>(i[j]));
        }


#pragma omp parallel
    for (int x = std::max(bboxmin[0], 0); x <= std::min(bboxmax[0], image.width() - 1); x++) {
        for (int y = std::max(bboxmin[1], 0); y <= std::min(bboxmax[1], image.height() - 1); y++) {
            vec3 bc_screen = barycentric(pts2, {static_cast<double>(x), static_cast<double>(y)});
            vec3 bc_clip = {bc_screen.x / pts[0][3], bc_screen.y / pts[1][3], bc_screen.z / pts[2][3]};
            bc_clip = bc_clip / (bc_clip.x + bc_clip.y + bc_clip.z);
            double frag_depth = vec3{clip_verts[0][2], clip_verts[1][2], clip_verts[2][2]} * bc_clip;
            if (bc_screen.x < 0 || bc_screen.y < 0 || bc_screen.z < 0 || frag_depth > zbuffer[x + y * image.width()])
                continue;
            TGAColor color;
            if (shader.fragment(bc_clip, color)) continue; // fragment shader can discard current fragment
            zbuffer[x + y * image.width()] = frag_depth;
            image.set(x, y, color);
        }
    }
}
